package common

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
)

type CloseChan struct {
	cs chan struct{}
	c  bool
	m  *sync.RWMutex
	n  int
}

func NewCloseChan(n int) *CloseChan {
	return &CloseChan{
		cs: make(chan struct{}, n),
		c:  false,
		m:  &sync.RWMutex{},
		n:  n,
	}
}

func (cc *CloseChan) IsClose() bool {
	cc.m.RLock()
	defer cc.m.RUnlock()
	return cc.c
}

func (cc *CloseChan) Close() {
	cc.m.Lock()
	defer cc.m.Unlock()
	if cc.c {
		return
	}
	cc.c = true
	close(cc.cs)
}

func (cc *CloseChan) Reset() {
	cc.m.Lock()
	defer cc.m.Unlock()
	cc.c = false
	cc.cs = make(chan struct{}, cc.n)
}

func (cc *CloseChan) Chan() chan struct{} {
	cc.m.RLock()
	defer cc.m.RUnlock()
	return cc.cs
}

func (cc *CloseChan) String() string {
	cc.m.RLock()
	defer cc.m.RUnlock()
	c := len(cc.cs)
	if cc.c {
		c = -1
	}
	return fmt.Sprintf("%d/%d", c, cc.n)
}

type WaitGroup struct {
	sync.WaitGroup
	n int64
}

func (this *WaitGroup) Done() {
	n := atomic.LoadInt64(&this.n)
	if n > 0 {
		atomic.AddInt64(&this.n, int64(-1))
		this.WaitGroup.Done()
	}
}

func (this *WaitGroup) DoneAll() {
	for {
		n := atomic.LoadInt64(&this.n)
		if n > 0 {
			atomic.AddInt64(&this.n, int64(-1))
			this.WaitGroup.Done()
		} else {
			break
		}
	}
}

func (this *WaitGroup) Add(n int) {
	if n > 0 {
		atomic.AddInt64(&this.n, int64(n))
		this.WaitGroup.Add(n)
	}
}

func (this *WaitGroup) MayWait() {
	if atomic.LoadInt64(&this.n) > 0 {
		this.Wait()
	}
}

func (this *WaitGroup) WaitTimeout(timeout time.Duration) bool {
	isTimeout := false
	timer := time.AfterFunc(timeout, func() {
		this.DoneAll()
		isTimeout = true
	})
	defer timer.Stop()

	this.Wait()
	return isTimeout
}

type LimitRun struct {
	signal chan struct{}
	n      int
}

func NewLimitRun(n int) *LimitRun {
	lrun := &LimitRun{
		signal: make(chan struct{}, n),
		n:      n,
	}
	for i := 0; i < n; i++ {
		lrun.signal <- struct{}{}
	}

	return lrun
}

func (lrun *LimitRun) Wait() {
	_, ok := <-lrun.signal
	if !ok {
		//锁定
		time.Sleep(100000 * 1e9)
	}
}

func (lrun *LimitRun) Signal() {
	lrun.signal <- struct{}{}
}

func (lrun *LimitRun) String() string {
	return fmt.Sprintf("Signal: %d/%d", len(lrun.signal), lrun.n)
}
